package com.gotobject.junit.internal.runners.statements;

import com.gotobject.junit.internal.benchmark.Benchmark;
import com.gotobject.junit.internal.runners.model.Explosion;
import com.gotobject.junit.internal.runners.model.Measure;
import org.junit.runners.model.Statement;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author Huascar A. Sanchez (huascar.sanchez@gmail.com)
 */
abstract class FailOnExplosion extends Statement {
    private final Statement next;

    /**
     * construct a {@code FailOnExplosion} statement.
     * @param next
     *      next statement in the execution chain.
     */
    protected FailOnExplosion(Statement next){
        this.next = next;
    }

    /**
     * a function responsible for capturing performance values
     * after a test has been run under a heavy explosion.
     * @param min
     *      executed time of a test.
     * @param max
     *      executed time of a test.
     * @param avg
     *      executed time of a test.
     */
    protected abstract void capture(long min, long max, long avg);

    /**
     * explode a determined test using a heavy load.
     * @param each retrieved explosion.
     * @param considerResponse indicates that expected response time value
	 * 		will be enforced.
	 * @throws Exception when the test did not support a given explosion.
     */
    protected void explode(Explosion each, boolean considerResponse) throws Exception {
        final int  users  					= each.getUsers();
        final long expectedResponseTime 	= each.getResponseTime();

        final AtomicLong min       = new AtomicLong(9999999L);
        final AtomicLong max       = new AtomicLong(-1L);
        final AtomicLong sum       = new AtomicLong(0L);
        final AtomicLong avgTime   = new AtomicLong(0L);

        for(int user = 0; user < users; user ++){
            final long elapsed = new Benchmark(each.getIterations()).stress(
                    new Callable<Void>(){
                        public Void call() throws Exception {
                            try {
                                next.evaluate();
                            } catch (Throwable t) {
                                // we still want to capture the result of a failed explosion.
                                capture(min.get(), max.get(), avgTime.get());
                                throw new Exception(t);
                            }
                            return null;
                        }
                    }
            );

            if (elapsed >= 0L) {
                if (elapsed < min.get())
                    min.getAndSet(elapsed);
                if (elapsed > max.get())
                    max.getAndSet(elapsed);
                sum.addAndGet(elapsed);
            }
        }

        avgTime.set(sum.get() / (long)users);
        if(expectedResponseTime > 0L && considerResponse){
            if(max.get() > expectedResponseTime){
                throw new Exception(String.format("test did not meet response time requirements. "
                    + "It expected %d milliseconds, but finalized after %d milliseconds",
                    expectedResponseTime,
                    max.get()
                   )
                );
            }
        }

        capture(min.get(), max.get(), avgTime.get());
    }

    /**
     *
     * @return the data collected after a load explosion.
     */
    public abstract Measure toMeasure();
}
